/**
 * 坐标转换工具
 * 提供 WGS-84 和 GCJ-02 (火星坐标系) 之间的坐标转换功能
 */

/**
 * 将 WGS-84 坐标转换为 GCJ-02 (火星坐标系) 坐标
 * @param {number} lng - 经度
 * @param {number} lat - 纬度
 * @returns {Array<number>} - 转换后的 [经度, 纬度] 数组
 */
export const wgs84ToGcj02 = (lng: number, lat: number): [number, number] => {
  // 先将坐标反向转换，然后使用反向转换的结果计算 GCJ-02 坐标
  let reverse = gcj02ToWgs84(lng, lat);
  return [lng * 2 - reverse[0], lat * 2 - reverse[1]];
};

/**
 * 将 GCJ-02 (火星坐标系) 坐标转换为 WGS-84 坐标
 * @param {number} lng - 经度
 * @param {number} lat - 纬度
 * @returns {Array<number>} - 转换后的 [经度, 纬度] 数组
 */
export const gcj02ToWgs84 = (lng: number, lat: number): [number, number] => {
  // 地球椭球参数
  let epsilon = 0.006693421622965823; // 第一偏心率平方
  let radLat = (lat / 180) * Math.PI; // 纬度转弧度
  let radius = 6378245; // 地球赤道半径
  let magic = Math.sin(radLat);
  magic = 1 - epsilon * magic * magic;
  magic = Math.sqrt(magic);

  // 计算偏移量
  let x = lng - 105;
  let y = lat - 35;
  let a = ((20 * Math.sin(6 * x * Math.PI) + 20 * Math.sin(2 * x * Math.PI)) * 2) / 3;
  let dLng = 300 + x + 2 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
  dLng += a;
  dLng += ((20 * Math.sin(x * Math.PI) + 40 * Math.sin((x / 3) * Math.PI)) * 2) / 3;
  dLng += ((150 * Math.sin((x / 12) * Math.PI) + 300 * Math.sin((x / 30) * Math.PI)) * 2) / 3;
  let dLat = -100 + 2 * x + 3 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
  dLat += a;
  dLat += ((20 * Math.sin(y * Math.PI) + 40 * Math.sin((y / 3) * Math.PI)) * 2) / 3;
  dLat += ((160 * Math.sin((y / 12) * Math.PI) + 320 * Math.sin((y / 30) * Math.PI)) * 2) / 3;

  // 转换为度数
  dLng = (dLng * 180) / ((radius / magic) * Math.cos(radLat) * Math.PI);
  dLat = (dLat * 180) / (((radius * (1 - epsilon)) / (magic * magic)) * Math.PI);
  return [lng - dLng, lat - dLat];
};

// 定义坐标类型
export interface Point {
  lng: number;
  lat: number;
}

/**
 * 批量转换 BD-09 坐标数组到 GCJ-02
 * @param bdPoints 百度坐标点数组
 * @returns GCJ-02 坐标点数组
 */
export const batchBd09ToGcj02 = (bdPoints: Point[]): Point[] => {
  return bdPoints.map(point => bd09ToGcj02(point));
};

/**
 * 将百度坐标系 (BD-09) 转换为火星坐标系 (GCJ-02)
 * @param bdPoint 百度坐标点 { lng: number, lat: number }
 * @returns GCJ-02 坐标点 { lng: number, lat: number }
 */
export const bd09ToGcj02 = (bdPoint: Point): Point => {
  // 常量定义
  const X_PI: number = (Math.PI * 3000.0) / 180.0;

  // 解构坐标值
  const { lng: bdLon, lat: bdLat } = bdPoint;

  // 坐标转换算法
  const x: number = bdLon - 0.0065;
  const y: number = bdLat - 0.006;

  const z: number = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
  const theta: number = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);

  // 计算结果
  const gcjLon: number = z * Math.cos(theta);
  const gcjLat: number = z * Math.sin(theta);

  return { lng: gcjLon, lat: gcjLat };
};

/**
 * 批量转换 GCJ-02 坐标数组到 BD-09
 * @param gcjPoints 火星坐标点数组
 * @returns 百度坐标点数组
 */
export const batchGcj02ToBd09 = (gcjPoints: Point[]): Point[] => {
  return gcjPoints.map(point => gcj02ToBd09(point));
};
/**
 * GCJ-02 转 BD-09 坐标系
 * @param gcjPoint 火星坐标点 { lng: number, lat: number }
 * @returns 百度坐标点 { lng: number, lat: number }
 */

const X_PI: number = (Math.PI * 3000.0) / 180.0;
export const gcj02ToBd09 = (gcjPoint: Point): Point => {
  // 解构坐标值
  const { lng: gcjLon, lat: gcjLat } = gcjPoint;

  // 坐标转换算法
  const x: number = gcjLon;
  const y: number = gcjLat;

  const z: number = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * X_PI);
  const theta: number = Math.atan2(y, x) + 0.000003 * Math.cos(x * X_PI);

  // 计算结果并添加固定偏移
  const bdLon: number = z * Math.cos(theta) + 0.0065;
  const bdLat: number = z * Math.sin(theta) + 0.006;

  return { lng: bdLon, lat: bdLat };
};

export const coordinateConverter = {
  wgs84ToGcj02,
  gcj02ToWgs84,
  gcj02ToBd09,
  batchGcj02ToBd09,
  bd09ToGcj02,
  batchBd09ToGcj02
};
